# == Schema Information
# Schema version: 14
#
# Table name: users
#
#  id                        :integer(11)     not null, primary key
#  login                     :string(255)     
#  email                     :string(255)     
#  crypted_password          :string(40)      
#  salt                      :string(40)      
#  created_at                :datetime        
#  updated_at                :datetime        
#  remember_token            :string(255)     
#  remember_token_expires_at :datetime        
#  activation_code           :string(40)      
#  activated_at              :datetime        
#  default_command_id        :integer(11)     
#  first_name                :string(255)     
#  last_name                 :string(255)     
#  per_page                  :integer(11)     
#

require 'digest/sha1'
class User < ActiveRecord::Base
  include ActionController::UrlWriter
  
  has_many :commands
  has_many :user_commands, :dependent => :destroy
  has_many :queries, :dependent=>:destroy
  has_many :tags, :through => :user_commands
  
  belongs_to :default_command, :class_name => "UserCommand", :foreign_key => :default_command_id
  acts_as_cached
  
  PUBLIC_USER = 'public' #can query all public commands + gets deleted user commands
  VIEWABLE_SQL = %[users.activation_code IS NULL]
  #these named_scope methods are just for console since they work intermittently with paginate (only for this model)
  named_scope :any
  named_scope :active, :conditions => VIEWABLE_SQL
  
  # Virtual attribute for the unencrypted password
  attr_accessor :password
  attr_protected :is_admin, :crypted_password, :salt

  validates_presence_of     :login, :email
  validates_presence_of     :password,                   :if => :password_required?
  validates_presence_of     :password_confirmation,      :if => :password_required?
  validates_length_of       :password, :within => 4..40, :if => :password_required?
  validates_confirmation_of :password,                   :if => :password_required?
  validates_uniqueness_of   :login, :email, :case_sensitive => false
  validates_length_of       :login,    :within => 1..40
  validates_format_of       :login,  :with => /[a-zA-Z0-9_]{1,16}/
  validates_length_of       :email,  :within => 3..100
  validates_format_of       :email,  :with => /^([^@\s]+)@(?:[-a-z0-9]+\.)+[a-z]{2,}$/i
  before_save :encrypt_password
  before_create :make_activation_code
  after_create :expire_class_caches
  after_destroy :move_commands_to_public_user, :expire_class_caches
  
  def validate
    if self.login && USER_STOPWORDS.include?(self.login.downcase)
      errors.add_to_base "Sorry, the username you've chosen (#{self.login}) is reserved by the system. Please use something else."
    end
  end
  
  #takes ownership of deleted commands
  def self.public_user; get_cache(:public_user) { find_by_login(PUBLIC_USER); } end
  
  def self.find_top_users(options={})
    #includes user_commands count
    users = find(:all, {:conditions=>VIEWABLE_SQL, :joins => "INNER JOIN user_commands ON user_commands.user_id = users.id", 
      :select => "users.*, count(users.id) as user_commands_count",:group => "user_commands.user_id HAVING user_commands_count >= 15",
      :order=>'login'}.merge(options))
    set_queries_count_for_users(users)
    users
  end
  
  def self.paginate_users(options={})
    #includes user_commands count
    User.paginate({:conditions=>VIEWABLE_SQL, :joins => "INNER JOIN user_commands ON user_commands.user_id = users.id", 
      :select => "users.*, count(users.id) as user_commands_count",:group => "user_commands.user_id",
      :order=>'login'}.merge(options))
  end
  
  def self.set_queries_count_for_users(users)
    user_queries = query_count_by_user_id
    users.each do |u|
      u[:queries_count] = user_queries[u.id] || 0
    end
  end
  
  def self.query_count_by_user_id
    hash = {}
    Query.find(:all, :group=>'user_id', :select=>'user_id, count(*) as count').each do |q|
      hash[q.user_id] = q.count.to_i
    end
    hash
  end
  
  def subscribe_to(command)
    self.user_commands.create(:command_id=>command.id, :url_options=>command.url_options)
  end
  
  def set_default_url_options
    default_url_options[:host] = ::HOST
  end
  
  def create_default_user_commands
    set_default_url_options
    default_commands_config.each do |e|
      ucommand = self.user_commands.create!(e.slice(:command_id))
      ucommand.update_tags(e[:tags]) if e[:tags]
    end
  end
  
  def default_command?
    return false if default_command_id.nil?
    return false if default_command.nil?
    true
  end
  
  #WARNING: removing this will break several user routes
  #specifies the value used by user objects in routing
  def to_param
    login
  end  
  
  # Remaining public methods generated by restful_authentication
  #------------------------------------------------------------------------------------------------------------------
  
  # Activates the user in the database.
  def activate
    @activated = true
    self.attributes = {:activated_at => Time.now.utc, :activation_code => nil}
    save(false)
  end

  def activated?
    activation_code.nil?
  end

  # Returns true if the user has just been activated.
  def recently_activated?
    @activated
  end
  
  # Authenticates a user by their login name and unencrypted password.  Returns the user or nil.
  def self.authenticate(login, password)
    u = find :first, :conditions => ['login = ? OR email = ?', login, login] # need to get the salt
    u && u.authenticated?(password) ? u : nil
  end

  # Encrypts some data with the salt.
  def self.encrypt(password, salt)
    Digest::SHA1.hexdigest("--#{salt}--#{password}--")
  end

  # Encrypts the password with the user salt
  def encrypt(password)
    self.class.encrypt(password, salt)
  end

  def authenticated?(password)
    crypted_password == encrypt(password)
  end

  def remember_token?
    remember_token_expires_at && Time.now.utc < remember_token_expires_at 
  end

  # These create and unset the fields required for remembering users between browser closes
  def remember_me
    remember_me_for 1.month
  end

  def remember_me_for(time)
    remember_me_until time.from_now.utc
  end

  def remember_me_until(time)
    self.remember_token_expires_at = time
    self.remember_token            = encrypt("#{email}--#{remember_token_expires_at}")
    save(false)
  end

  def forget_me
    self.remember_token_expires_at = nil
    self.remember_token            = nil
    save(false)
  end

  protected
    #only command_id and tags necessary, all other fields are grabbed from the actual command record
    #name field is there for easy identification
    def default_commands_config
      [
      {
        :name => "Google Quicksearch", 
        :command_id=> 1,
        :tags=>'google'
      },
      {
        :name => "Gmail Search", 
        :command_id=> 2,
        :tags=>'google mail'
      },
      {
        :name => "Google \"I'm Feeling Lucky\" Wikipedia (en) search",
        :command_id=> 3,
        :tags=>'google wikipedia'
      },
      {
        :name => "Dictionary Lookup at Reference.com",
        :command_id=> 4,
        :tags=>"dictionary reference language english"
      },
      {
        :name => "My Queriac Page",
        :command_id=> 5,
        :tags=>"queriac bootstrap"
      },
      {
        :name => "Show a Queriac user command",
        :command_id=> 6,
        :tags=>"queriac bootstrap"
      },
      {
        :name => "Edit a Queriac user command",
        :command_id=> 7,
        :tags=>"queriac bootstrap"
      },
      {
        :name => "Create a new Queriac user command",
        :command_id=> 8,
        :tags=>"queriac bootstrap"
      },
      {
        :name=> "Search my Queriac user commands",
        :command_id=> 645,
        :tags=>"queriac bootstrap"
      }
      ]
    end
    
    def password_required?
      crypted_password.blank? || !password.blank?
    end
    
    # Callback methods
    def encrypt_password
      return if password.blank?
      self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
      self.crypted_password = encrypt(password)
      true
    end

    def make_activation_code
      self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
      true
    end 
    
    def move_commands_to_public_user
      if self.commands.size > 0 && (public_user = self.class.public_user)
        self.commands.each {|e| e.update_attribute :user_id, public_user.id}
      end
      true
    end
    
    def expire_class_caches
      self.class.expire_cache :find_top_users
      true
    end
    
    #publicity should be public or any
    def user_commands_by_type(publicity)
      quicksearches = self.user_commands.send(publicity).quicksearches.used.find(:all, {:order => "queries_count DESC", :include => [:user], :limit => 15})
      shortcuts = self.user_commands.send(publicity).shortcuts.used.find(:all, {:order => "queries_count DESC", :include => [:user,], :limit => 15})
      bookmarklets = self.user_commands.send(publicity).bookmarklets.used.find(:all, {:order => "queries_count DESC", :include => [:user], :limit => 15})
      return [quicksearches, shortcuts, bookmarklets]
    end
end
